﻿//#define USE_DUNGEONBUDDY_DLL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.Helpers;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;
using Bots.DungeonBuddyDll.Avoidance;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif

{
    public class Scholomance_Heroic : Dungeon
    {
        #region Overrides of Dungeon

        public override uint DungeonId
        {
            get { return 472; }
        }

        public override WoWPoint Entrance
        {
            get { return new WoWPoint(1279.48, -2551.52, 87.41); }
        }

        public override WoWPoint ExitLocation
        {
            get { return new WoWPoint(188.05, 126.49, 138.82); }
        }

        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                ret =>
                {
                    var unit = ret as WoWUnit;
                    if (unit != null)
                    {
                        if (unit.Entry == WovenBoneguardId && unit.Combat && Me.Combat && !unit.IsTargetingMeOrPet && !unit.IsTargetingMyPartyMember)
                            return true;
                        if (unit.Entry == LiliansSoulId && Me.IsMelee() && _isFixated)
                            return true;
                        if (unit.Entry == ExpiredTestSubjectId && Me.IsTank() && unit.CurrentTargetGuid > 0)
                        {
                            var expiredTestSubjectTarget = unit.CurrentTarget as WoWPlayer;
                            if (expiredTestSubjectTarget != null && IsTakingHarshLesson(expiredTestSubjectTarget))
                                return true;
                        }
                        if (unit.Entry == DarkmasterGandlingId && IsTakingHarshLesson(Me))
                            return true;
                    }
                    return false;
                });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == BoneweaverId && Me.IsTank() && Me.Combat && !ScriptHelpers.IsBossAlive("Jandice Barov"))
                        outgoingunits.Add(unit);

                    if (unit.Entry == InstructorChillheartsPhylactery)
                        outgoingunits.Add(unit);

                    if (unit.Entry == JandiceBarovSpawnId)
                        outgoingunits.Add(unit);

                    if (unit.Entry == ExpiredTestSubjectId && IsTakingHarshLesson(Me))
                        outgoingunits.Add(unit);
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == BoneweaverId)
                        priority.Score += 500;

                    if (unit.Entry == CandlestickMageId && StyxWoW.Me.IsDps())
                        priority.Score += 500;

                    if (unit.Entry == JandiceBarovSpawnId && unit.HasAura("Puff of Smoke"))
                        priority.Score += 5000;

                    if (unit.Entry == MeatGraftId && StyxWoW.Me.IsDps())
                        priority.Score += 500;
                }
            }
        }

        #endregion

        private const uint InstructorChillheartsPhylactery = 58664;
        private const uint CandlestickMageId = 59467;
        private const uint JandiceBarovSpawnId = 59220;
        private const uint BoneweaverId = 59193;
        private const uint BonePileId = 59304;
        private const uint SoulflameId = 59316;
        private const uint MeatGraftId = 59982;
        private const uint LiliansSoulId = 58791;
        private const uint WovenBoneguardId = 59213;
        private const uint LilianVossId = 58722;
        private const int HarshLessonId = 113420;
        private const uint ExpiredTestSubjectId = 59100;
        private const uint FreshTestSubjectId = 59099;
        private const uint BoredStudentId = 59614;
        const uint DarkmasterGandlingId = 59080;
        private bool _isFixated;

        private static LocalPlayer Me
        {
            get { return StyxWoW.Me; }
        }

        [EncounterHandler(0, "Root")]
        public Composite RootEncounter()
        {
            return new PrioritySelector();
        }

        [EncounterHandler(64563, "Talking Skull", Mode = CallBehaviorMode.Proximity)]
        public Composite TalkingSkullEncounter()
        {
            return new PrioritySelector(
                new Decorator<WoWUnit>(unit => unit.QuestGiverStatus == QuestGiverStatus.Available, ScriptHelpers.CreatePickupQuest(64563)),
                new Decorator<WoWUnit>(unit => unit.QuestGiverStatus == QuestGiverStatus.TurnIn, ScriptHelpers.CreateTurninQuest(64563)));
        }


        // pickup quest items.
        [ObjectHandler(214279, "In the Shadow of the Light", ObjectRange = 45)]
        [ObjectHandler(214278, "Kel'Thuzad's Deep Knowledge", ObjectRange = 45)]
        [ObjectHandler(214280, "Forbidden Rites and other Rituals Necromantic", ObjectRange = 45)]
        [ObjectHandler(214277, "The Dark Grimoire", ObjectRange = 45)]
        public Composite TheFourTombsHandler()
        {
            WoWGameObject questObject = null;
            var questObjectiveIndex = new Dictionary<uint, int>
            {
                {214279, 0}, // In the Shadow of the Light
                {214278, 1}, // Kel'Thuzad's Deep Knowledge
                {214280, 2}, // Forbidden Rites and other Rituals Necromantic
                {214277, 3} // The Dark Grimoire
            };
            const int theFourTomesId = 31442;
            WoWDescriptorQuest quest;

            return new PrioritySelector(
                ctx => questObject = ctx as WoWGameObject,
                new Decorator(
                    ctx =>
                    StyxWoW.Me.QuestLog.ContainsQuest(theFourTomesId) && !StyxWoW.Me.QuestLog.GetQuestById(theFourTomesId).IsCompleted &&
                    Me.QuestLog.GetQuestById(theFourTomesId).GetData(out quest) && quest.ObjectivesDone[questObjectiveIndex[questObject.Entry]] == 0 &&
                    Targeting.Instance.IsEmpty() && !ScriptHelpers.WillPullAggroAtLocation(questObject.Location) && Me.Location.PathDistance(questObject.Location) <= 45,
                    new PrioritySelector(
                        new Decorator(ctx => !questObject.WithinInteractRange, new Action(ctx => Navigator.MoveTo(questObject.Location))),
                        new Decorator(
                            ctx => questObject.WithinInteractRange,
                            new Sequence(
                                new Action(ctx => questObject.Interact()),
                                new WaitContinue(3, ctx => LootFrame.Instance.IsVisible, new ActionAlwaysSucceed()),
                                new Action(ctx => Logger.Write("Looting {0}", questObject.Name)),
                                new Action(ctx => LootFrame.Instance.LootAll()))))));
        }

        [EncounterHandler(58633, "Instructor Chillheart", Mode = CallBehaviorMode.Proximity, BossRange = 80)]
        public Composite InstructorChillheartEncounter()
        {
            const uint iceWallId = 62731;
            const uint arcaneBombId = 58753;
            const uint antonidasSelfHelpGuideToStandingInFireId = 58635;
            const uint frigidGraspId = 58640;

            var phase1TankLoc = new WoWPoint(203.2554, 38.66046, 119.2258);
            const uint risenGuard = 58822;

            WoWUnit boss = null;
            AddAvoidObject(ctx => true, 5, frigidGraspId);
            AddAvoidObject(ctx => true, 3, u => u is WoWPlayer && !u.IsMe && ((WoWPlayer)u).HasAura("Ice Wrath"));
            AddAvoidObject(ctx => true, 4, arcaneBombId, antonidasSelfHelpGuideToStandingInFireId);
            // avoid the icewall. 
            AddAvoidObject(
                ctx => true,
                8,
                u => u.Entry == iceWallId,
                o =>
                { // the icewall object is just one NPC so we need to use its y location and use our x location 
                    var loc = o.Location;
                    loc.X = Me.X;
                    return loc;
                });
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                // OOC behavior
                new Decorator(
                    ctx => !boss.Combat,
                    new PrioritySelector(
                // kill all risen guards 
                        ScriptHelpers.CreateClearArea(() => boss.Location, 70, u => u.Entry == risenGuard))),
                // Phase 1 combat behavior
                new Decorator(
                    ctx => boss.Combat && !boss.HasAura("Permanent Feign Death"),
                    new PrioritySelector(
                // move to the west wall 
                        new Decorator(
                            ctx => StyxWoW.Me.IsRange() && StyxWoW.Me.Y < 30,
                            new Action(
                                ctx =>
                                {
                                    var loc = StyxWoW.Me.Location;
                                    loc.Y = 35;
                                    Navigator.MoveTo(loc);
                                })),
                        ScriptHelpers.CreateTankUnitAtLocation(() => phase1TankLoc, 5))),
                // Phase 2 combat behavior
                new Decorator(ctx => boss.Combat && boss.HasAura("Permanent Feign Death"), new PrioritySelector()));
        }

        [EncounterHandler(59184, "Jandice Barov", Mode = CallBehaviorMode.CurrentBoss)]
        public Composite JandiceBarovIllusionEncounter()
        {
            var combatTimer = new WaitTimer(TimeSpan.FromSeconds(6));

            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ctx => boss != null && boss.Combat,
                    new Action(
                        ctx =>
                        {
                            combatTimer.Reset();
                            return RunStatus.Failure;
                        })),
                // handle vanish.
                new Decorator(ctx => boss == null && !combatTimer.IsFinished && StyxWoW.Me.IsTank(), new ActionAlwaysSucceed()));
        }

        [EncounterHandler(59184, "Jandice Barov")]
        public Composite JandiceBarovEncounter()
        {
            const int wondrousRapidityId = 114062;
            const uint gravityFluxId = 114035;
            WoWUnit boss = null;
            AddAvoidObject(ctx => true, 8, o => o is WoWAreaTrigger && ((WoWAreaTrigger)o).SpellId == gravityFluxId);
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                ScriptHelpers.CreateAvoidUnitAnglesBehavior(
                    ctx => Me.IsTank() && boss.CastingSpellId == wondrousRapidityId || !Me.IsTank(), () => boss, new ScriptHelpers.AngleSpan(0, 90)));
        }

        [EncounterHandler(59153, "Rattlegore", BossRange = 100, Mode = CallBehaviorMode.Proximity)]
        public Composite RattlegoreEncounter()
        {
            WoWUnit boss = null;
            WoWUnit bonePile = null;
            var waitLoc = new WoWPoint(261.0993, 64.81649, 113.4927);
            // run from soul flame.
            AddAvoidObject(ctx => true, obj => obj.Scale + 1, SoulflameId);
            return new PrioritySelector(
                ctx =>
                {
                    var soulFlames = ObjectManager.GetObjectsOfType<WoWUnit>().Where(u => u.Entry == SoulflameId).ToArray();
                    bonePile =
                        ObjectManager.GetObjectsOfType<WoWUnit>().Where(u => u.Entry == BonePileId && soulFlames.All(s => s.Location.Distance(u.Location) > s.Scale + 1)).
                            OrderBy(u => u.DistanceSqr).FirstOrDefault();
                    return boss = ctx as WoWUnit;
                },
                // move inside the gate.
                new Decorator(
                    ctx => !ScriptHelpers.IsBossAlive("Jandice Barov") && !Me.IsTank() && ScriptHelpers.Tank != null && ScriptHelpers.Tank.Y >= 61 && Me.Y < 61f,
                    new Action(ctx => Navigator.MoveTo(waitLoc))),
                new Decorator(
                    ctx => Me.IsTank() && !Me.Combat && Me.Y >= 61 && Me.PartyMembers.Count(p => p.Y >= 61f) < 4,
                    new Sequence(new ActionSetActivity("Waiting for other party members to enter room at Rattlegore"), new Action(ctx => Navigator.MoveTo(waitLoc)))),
                // get the bone pile buff.
                new Decorator(
                    ctx => !Me.HasAura("Bone Armor") && bonePile != null && (Me.IsTank() && boss.CurrentTargetGuid == Me.Guid || !Me.IsTank()),
                    new PrioritySelector(
                        new Decorator(ctx => bonePile.WithinInteractRange, new Action(ctx => bonePile.Interact())),
                        new Decorator(ctx => !bonePile.WithinInteractRange, new Action(ctx => Navigator.MoveTo(bonePile.Location))))));
        }

        [EncounterHandler(58722, "Lilian Voss")]
        public Composite LilianVossEncounter()
        {
            const uint darkBlazeId = 58780;
            const int deathsGraspId = 111570;
            const int shadowShivId = 111775;
            var runFromBossTimer = new WaitTimer(TimeSpan.FromSeconds(3));
            var entLoc = new WoWPoint(200.3293, 104.8955, 108.2328);
            WoWUnit boss = null;
            var roomCenterLoc = new WoWPoint(200.4618, 86.05209, 107.7617);
            // prevent bot from going into hallway during encounter.
            AddAvoidLocation(ctx => boss != null && boss.IsValid && boss.Combat && Me.Y <= 104, 4, o => entLoc);
            // run from from the fire on ground. ignore it if moving while fixated.
            AddAvoidObject(ctx => _isFixated && !Me.IsMoving || !_isFixated, () => roomCenterLoc, 25, 2, darkBlazeId);
            // run away from boss after she casts deaths grasp.
            AddAvoidObject(ctx => boss.CastingSpellId != deathsGraspId && !runFromBossTimer.IsFinished, 8, u => u == boss);
            // run from shadow shiv target
            AddAvoidObject(
                ctx => true,
                8,
                u =>
                u is WoWPlayer && (boss != null && boss.IsValid && boss.CastingSpellId == shadowShivId && boss.CurrentTargetGuid == u.Guid || u.ToUnit().HasAura(shadowShivId)));
            return new PrioritySelector(
                ctx =>
                {
                    boss = ctx as WoWUnit;
                    if (boss.CastingSpellId == deathsGraspId)
                        runFromBossTimer.Reset();
                    return boss;
                },
                // new Decorator(ctx => )
                new Decorator(ctx => Me.Y > 104, new Action(ctx => Navigator.PlayerMover.MoveTowards(roomCenterLoc))),
                new Decorator(ctx => Me.IsTank() && boss.IsTargetingMeOrPet, ScriptHelpers.CreateTankUnitAtLocation(() => roomCenterLoc, 20)));
        }

        [EncounterHandler(58791, "Lilian's Soul")]
        public Composite LiliansSoulEncounter()
        {
            const int fixateAngerId = 115350;
            WoWUnit boss = null;
            var roomCenterLoc = new WoWPoint(200.4618, 86.05209, 107.7617);

            // run from bot if fixated. _isFixated is updated in the behavior.
            AddAvoidObject(ctx => _isFixated, () => roomCenterLoc, 25, 10, LiliansSoulId);
            return new PrioritySelector(
                ctx =>
                {
                    boss = ctx as WoWUnit;
                    _isFixated = boss.CastingSpellId == fixateAngerId && boss.CurrentTargetGuid == Me.Guid || Me.HasAura(115350);
                    return boss;
                },
                // stay out of the hall to avoid getting cornered.
                new Decorator(ctx => Me.Y > 104, new Action(ctx => Navigator.PlayerMover.MoveTowards(roomCenterLoc))),
                new Decorator(ctx => Me.IsTank() && Targeting.Instance.IsEmpty(), new ActionAlwaysSucceed()));
        }

        [ObjectHandler(211278, "Coffer of Forgotten Souls")]
        public Composite CofferofForgottenSoulsHandler()
        {
            WoWGameObject chest = null;
            return new PrioritySelector(ctx => chest = ctx as WoWGameObject, new Decorator(ctx => chest.CanLoot, ScriptHelpers.CreateInteractWithObject(() => chest)));
        }


        [EncounterHandler(59613, "Professor Slate", Mode = CallBehaviorMode.Proximity, BossRange = 90)]
        public Composite ProfessorSlateEncounter()
        {
            var boredStudenPullToLoc = new WoWPoint(144.916, 73.33847, 106.3931);

            WoWUnit boss = null, boredStudent = null;
            const uint toxicPoison = 114873;
            AddAvoidObject(ctx => true, 4, toxicPoison);

            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(ctx => !boss.Combat,
                    new PrioritySelector(
                        ctx => boredStudent = ObjectManager.GetObjectsOfType<WoWUnit>()
                        .Where(u => u.Entry == BoredStudentId && u.IsAlive)
                        .OrderBy(u => u.Location.PathDistance(Me.Location))
                        .FirstOrDefault(),
                        ScriptHelpers.CreatePullNpcToLocation(ctx => boredStudent != null, () => boredStudent, () => boredStudenPullToLoc, 6))),

              new Decorator(ctx => boss.Combat,
                    new PrioritySelector(
                        ScriptHelpers.CreateTankFaceAwayGroupUnit(() => boss, 15),
                        ScriptHelpers.CreateAvoidUnitAnglesBehavior(ctx => !Me.IsTank() && boss.Distance < 15, () => boss, new ScriptHelpers.AngleSpan(0, 60)))));
        }


        [EncounterHandler(59080, "Darkmaster Gandling", BossRange = 300)]
        public Composite DarkmasterGandlingEncounter()
        {
            AddAvoidObject(ctx => IsTakingHarshLesson(Me) && Me.IsHealer(), 8, u => u.Entry == ExpiredTestSubjectId && u.ToUnit().IsTargetingMeOrPet);
            // avoid running into doors when having a harse lesson.
            AddAvoidObject(ctx => IsTakingHarshLesson(Me), 4, u => u is WoWGameObject && u.ToGameObject().SubType == WoWGameObjectType.Door && u.ZDiff < 10);
            WoWSpell dispell = null;
            WoWUnit dispellTarget = null;

            WoWUnit[] freshTestSubjects = null;

            return new PrioritySelector(
                ScriptHelpers.CreateDispellParty("Immolate", ScriptHelpers.PartyDispellType.Magic),
                new Decorator(
                // healer behavior for harsh lesson.
                    ctx => IsTakingHarshLesson(Me) && Me.IsHealer(),
                    new PrioritySelector(
                        ctx =>
                        {
                            freshTestSubjects =
                                ObjectManager.GetObjectsOfType<WoWUnit>().Where(
                                    u => u.Entry == FreshTestSubjectId && u.HasAura("Explosive Pain") && Me.Location.PathDistance(u.Location) < 50).OrderBy(
                                        u => u.DistanceSqr).ToArray();

                            dispellTarget =
                                freshTestSubjects.FirstOrDefault(
                                    u => ObjectManager.GetObjectsOfType<WoWUnit>().Count(o => o.Entry == ExpiredTestSubjectId && o.Location.Distance(u.Location) < 8) >= 1);

                            dispell = GetDefensiveMagicDispell();
                            return dispellTarget;
                        },
                        new Decorator(
                            ctx => dispellTarget != null && dispell != null && SpellManager.CanCast(dispell, dispellTarget, true, false),
                            new Action(ctx => SpellManager.Cast(dispell, dispellTarget))),
                        new Decorator(
                            ctx => dispellTarget != null && dispell != null && (dispellTarget.Distance > 30 || !dispellTarget.InLineOfSpellSight),
                            new Action(ctx => Navigator.MoveTo(dispellTarget.Location))),
                        new Decorator(
                            ctx => dispellTarget == null && dispell != null && freshTestSubjects.Length > 0 && freshTestSubjects[0].Distance > 4,
                            new Action(ctx => Navigator.MoveTo(freshTestSubjects[0].Location))))));
        }

        readonly Dictionary<ulong, HarshLessonLogEntry> _partyHarshLessonsLog = new Dictionary<ulong, HarshLessonLogEntry>();

        bool IsTakingHarshLesson(WoWPlayer player)
        {
            _partyHarshLessonsLog.RemoveAll( v => !v.IsValid);

            if (!_partyHarshLessonsLog.ContainsKey(player.Guid))
                _partyHarshLessonsLog[player.Guid] = new HarshLessonLogEntry(player);


            return _partyHarshLessonsLog[player.Guid].HasHarshLession;
        }


        public static WoWSpell GetDefensiveMagicDispell()
        {
            switch (StyxWoW.Me.Class)
            {
                case WoWClass.Druid:
                    return SpellManager.HasSpell("Remove Corruption") && StyxWoW.Me.HasAura("Nature's Cure") ? SpellManager.Spells["Remove Corruption"] : null;
                case WoWClass.Monk:
                    return StyxWoW.Me.Specialization == WoWSpec.MonkMistweaver && SpellManager.HasSpell("Detox") ? SpellManager.Spells["Detox"] : null;
                case WoWClass.Paladin:
                    return SpellManager.HasSpell("Cleanse") && StyxWoW.Me.HasAura("Sacred Cleansing") ? SpellManager.Spells["Cleanse"] : null;
                case WoWClass.Priest:
                    return SpellManager.HasSpell("Purify") ? SpellManager.Spells["Purify"] : null;
                case WoWClass.Shaman:
                    return SpellManager.HasSpell("Cleanse Spirit") && StyxWoW.Me.HasAura("Improved Cleanse Spirit") ? SpellManager.Spells["Cleanse Spirit"] : null;
                default:
                    return null;
            }
        }

        class HarshLessonLogEntry
        {
            private readonly WoWPlayer _player;

            public HarshLessonLogEntry(WoWPlayer player)
            {
                _player = player;
            }

            readonly WaitTimer _harshLessonTimer = new WaitTimer(TimeSpan.FromSeconds(30));

            bool _hasHarshLesson;
            public bool HasHarshLession
            {
                get
                {
                    if (!IsValid) return false;

                    if (!_player.IsAlive) return false;

                    var hasHarshLessonAura = _player.HasAura(HarshLessonId);

                    if (!hasHarshLessonAura)
                    {
                        _hasHarshLesson = false;
                        return false;
                    }
                    if (!_hasHarshLesson)
                    {
                        _harshLessonTimer.Reset();
                        _hasHarshLesson = true;
                    }
                    return !_harshLessonTimer.IsFinished;               
                }
            }

            public bool IsValid
            {
                get { return _player.IsValid; }
            }
        }
    }
}